package Client.Model;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Observable;

import logic.Board;
import logic.ChatMessage;
import logic.OnlinePlayer;
import logic.PlayRequest;

import communication.ActionRequest;
import communication.ClientMessageID;
import communication.ConnectRequest;
import communication.ConnectResponse;
import communication.GameRequest;
import communication.ServerMessageID;

public class GameModel extends Observable{

	
	final public static int VIEW_INITIAL = 1;
	final public static int VIEW_CONNECTED = 2;
	final public static int VIEW_GAME = 3;
	
	final public static int STATE_NONE = 10;
	final public static int STATE_WAITING = 11;
	final public static int STATE_SETTING = 12;
	final public static int STATE_PLAYING = 13;
	final public static int STATE_FINISHED = 14;
	
	
	final public static int CONNECTION_ERROR = 20;
	final public static int LOGIN_OK = 21;
	final public static int LOGIN_INVALID = 22;
	final public static int LOGIN_ALREADY_LOGGED = 23;
	final public static int LOGIN_FULL = 24;
	final public static int REGISTER_OK = 25;
	final public static int REGISTER_ERROR = 26;
	final public static int REGISTER_USERNAME_EXISTS = 27;
	
	
	
	//-------------------------------------------------
	
	private int view;
	private int gamestate;
	
	private Socket socket = null;
	private ObjectInputStream in = null;
	private ObjectOutputStream out = null;
	
	
	boolean boardConfigured;
	boolean enemyBoardConfigured;
	
	boolean myTurn;
	
	@SuppressWarnings("unused")
	private String username="";
	private String enemy="";
	private boolean waiting;
	private ReceiveMessages t_receive_msg;
	
	private Board board = null;
	private Board enemyBoard = null;
	
	private ArrayList<OnlinePlayer> onlinePlayers;
	private ArrayList<ChatMessage> messages;
	private ArrayList<PlayRequest> playRequests;
	
	private ArrayList<Notification> notifications;
	
	
	//-------------------------------------------------
	
	public GameModel() {
		
		this.view = VIEW_INITIAL;
		this.gamestate = STATE_NONE;
		this.waiting = false;
	
		
		onlinePlayers = new ArrayList<OnlinePlayer>();
		messages = new ArrayList<ChatMessage>();
		playRequests = new ArrayList<PlayRequest>();
		notifications = new ArrayList<Notification>();
		
		setChanged();
		notifyObservers();
		
	}
	
	public ObjectInputStream getIn() {
		return in;
	}
	
	public ObjectOutputStream getOut() {
		return out;
	}
	
	public boolean isWaiting() {
		return waiting;
	}
	
	public void setWaiting(boolean value){
		waiting = value;
	}
	
	public void switchWaiting(){
		if (waiting==true)
			changeWaiting(false);
		else
			changeWaiting(true);
		
	}


	public ArrayList<OnlinePlayer> getOnlinePlayers() {
		return onlinePlayers;
	}
	
	public void setOnlinePlayers(ArrayList<OnlinePlayer> onlinePlayers) {
		this.onlinePlayers = onlinePlayers;
	}

	public int getView() {
		return view;
	}

	public int getGamestate() {
		return gamestate;
	}
	
	public Board getBoard() {
		return board;
	}
	
	public void setBoard(Board board) {
		this.board = board;
	}
	
	public ArrayList<ChatMessage> getMessages() {
		return messages;
	}
	
	public Board getEnemyBoard() {
		return enemyBoard;
	}
	
	public void setEnemyBoard(Board enemyBoard) {
		this.enemyBoard = enemyBoard;
	}
	
	public void setView(int view) {
		this.view = view;
	}

	public String getEnemy() {
		return enemy;
	}
	
	public void setEnemy(String enemy) {
		this.enemy = enemy;
	}
	
	public void setGamestate(int gamestate) {
		this.gamestate = gamestate;
	}

	public boolean isBoardConfigured() {
		return boardConfigured;
	}
	
	public boolean isEnemyBoardConfigured() {
		return enemyBoardConfigured;
	}
	
	public boolean isMyTurn() {
		return myTurn;
	}
	
	public void setBoardConfigured(boolean boardConfigured) {
		this.boardConfigured = boardConfigured;
	}
	
	public void setEnemyBoardConfigured(boolean enemyBoardConfigured) {
		this.enemyBoardConfigured = enemyBoardConfigured;
	}
	
	public void setMyTurn(boolean myTurn) {
		this.myTurn = myTurn;
	}
		
	public ArrayList<Notification> getNotifications() {
		return notifications;
	}
	
	public ArrayList<PlayRequest> getPlayRequests() {
		return playRequests;
	}
	
	public void setPlayRequests(ArrayList<PlayRequest> playRequests) {
		this.playRequests = playRequests;
	}
	
	public String getUsername() {
		return username;
	}
	
	/*----------------------------*/
	
	/**
	 * Cria socket e os object input/output streams para comunicar com o servidor
	 * 
	 * @param addr
	 * @param port
	 * @return true se foi bem sucedido ou false caso contrário
	 */
	private boolean connectServer(InetAddress addr, int port){
		
		try {
			
			 socket = new Socket(addr,port);
								
			 out = new ObjectOutputStream(socket.getOutputStream());
		     in = new ObjectInputStream(socket.getInputStream());
	    		
		} catch (IOException e) {
			System.out.println("connectServer exception: " + e);
			socket = null;
			return false;
		}
		
	     return true;
	}
	
	/**
	 * Sends a login request to the server and waits for the answear
	 * 
	 */
	public int doLogin(String username, String password, InetAddress addr, int port){		
		
		int res;
		
		if (socket==null || (socket!=null&&socket.isClosed()))
			if (!connectServer(addr, port))
					return CONNECTION_ERROR;
		
		ConnectRequest request = new ConnectRequest(username, password, ConnectRequest.REQ_LOGIN);
		ConnectResponse response;
		
		try {
			
			sendConnectRequest(request);
			
			@SuppressWarnings("unused")
			ServerMessageID smid = (ServerMessageID) in.readObject();
			response = (ConnectResponse)in.readObject();
		
		} catch (IOException e) {
			
			e.printStackTrace();
			try {
				socket.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			return CONNECTION_ERROR;
			
		}catch(ClassNotFoundException ex){
			
			ex.printStackTrace();
			
			try {
				socket.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			
			return CONNECTION_ERROR;
		}
		
		switch (response.getResponse()){
		
			case ConnectResponse.RESP_LOGIN_ALREADY_LOGGED:
				res = LOGIN_ALREADY_LOGGED;
				break;
			case ConnectResponse.RESP_LOGIN_FULL:
				res = LOGIN_FULL;
				break;
			case ConnectResponse.RESP_LOGIN_INVALID:
				res = LOGIN_INVALID;
				break;
			case ConnectResponse.RESP_LOGIN_OK:
				res = LOGIN_OK;
				this.username = request.getUsername();
				view = VIEW_CONNECTED;
				break;
			default:
				res = CONNECTION_ERROR;
				System.out.println("[ERROR] Invalid Response Code!");
		}
		
		setChanged();
		notifyObservers();
		
		if (view == VIEW_INITIAL){
		
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		else
			if (view==VIEW_CONNECTED){
				t_receive_msg = new ReceiveMessages(this);
				t_receive_msg.start();
			}
		
		return res;
		
	}
	
	/**
	 * send a request to regist a new account and if it is Ok, the client stays logged in
	 * 
	 */
	public int doRegister(String username, String password, InetAddress addr, int port){		
		
		int res;
		
		if (socket==null || (socket!=null&&socket.isClosed()))
			if (!connectServer(addr, port))
					return CONNECTION_ERROR;

		ConnectRequest request = new ConnectRequest(username, password, ConnectRequest.REQ_REGISTER);
		ConnectResponse response;
		@SuppressWarnings("unused")
		ServerMessageID smid;
		
		try {
			
			sendConnectRequest(request);
			
			smid = (ServerMessageID) in.readObject();
			response = (ConnectResponse)in.readObject();
			
		} catch (IOException e) {
			e.printStackTrace();
			return CONNECTION_ERROR;
			
		}catch(ClassNotFoundException ex){
			ex.printStackTrace();
			return CONNECTION_ERROR;
		}
		
		
		switch (response.getResponse()){
		
			case ConnectResponse.RESP_REGISTER_ERROR:
				res = REGISTER_ERROR;
				break;
			case ConnectResponse.RESP_REGISTER_USERNAME_ALREADY_EXISTS:
				res = REGISTER_USERNAME_EXISTS;
				break;
			case ConnectResponse.RESP_REGISTER_OK:
				res = REGISTER_OK;
				this.username = request.getUsername();
				view = VIEW_CONNECTED;
				break;
			default:
				res = CONNECTION_ERROR;
				System.out.println("[ERROR] Invalid Register Response Code!");
		}
		
		setChanged();
		notifyObservers();
		
		if (view==VIEW_INITIAL){
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		else
			if (view==VIEW_CONNECTED){
				t_receive_msg = new ReceiveMessages(this);
				t_receive_msg.start();
			}
		
		return res;
		
	}
	
	
	/**
	 * Update View / Notify observers
	 * 
	 */
	public void update(){
		setChanged();
		notifyObservers();
	}
	

	
	/**
	 * Do the logout process
	 * 
	 * @return 
	 * - false if cant logout
	 * - true if logout is done.
	 */
	public boolean doLogout(){		

		if (view!=VIEW_CONNECTED)
			return false;

		ActionRequest request = new ActionRequest(ActionRequest.REQ_LOGOUT);

		t_receive_msg.requestStop();

		sendActionRequest(request);		

		try {
			in.close();
			out.close();
			socket.close();
		} catch (IOException e) {}


		view = VIEW_INITIAL;
		gamestate = STATE_NONE;
		username = "";

		setChanged();
		notifyObservers();

		return true;

	}
	
	
	public void invitePlayer(String username){
		
		ActionRequest req = new ActionRequest(ActionRequest.REQ_INVITE);
		req.setUsername(username);
		
		sendActionRequest(req);
		
		playRequests.add(new PlayRequest(this.username, username));
		
		update();
		
	}
	
	public void acceptInvite(String username){
		
		ActionRequest req = new ActionRequest(ActionRequest.REQ_ACCEPT);
		req.setUsername(username);
		
		sendActionRequest(req);
		
		deletePlayRequestFrom(username);
		
		update();
	}
	
	public void uninvitePlayer(String username){
		

		ActionRequest req = new ActionRequest(ActionRequest.REQ_UNINVITE);
		req.setUsername(username);
		
		sendActionRequest(req);
		
		deletePlayRequestTo(username);
		update();
		
	}

	public void rejectInvite(String username){
		
		ActionRequest req = new ActionRequest(ActionRequest.REQ_REJECT);
		req.setUsername(username);
		
		sendActionRequest(req);
		
		deletePlayRequestFrom(username);
		
		update();
		
	}


	/**
	 * Send a request to play with a user that is Waiting for someone to play  with
	 * 
	 */
	public void playWith(String username){
		
		ActionRequest req = new ActionRequest(ActionRequest.REQ_PLAY_WITH);
		req.setUsername(username);
		
		sendActionRequest(req);
		
	}
	
	/**
	 * Set waiting (for someone to play) or not
	 * 
	 */
	public void changeWaiting(boolean waiting){
		
		ActionRequest req;
		
		if (!waiting)
			req = new ActionRequest(ActionRequest.REQ_CANCEL_WAITING);
		else
			req = new ActionRequest(ActionRequest.REQ_WAITING);

		this.waiting = waiting;

		sendActionRequest(req);
		
		setChanged();
		notifyObservers();
		
	}
	
	/**
	 * 
	 * Send Chat Message
	 */
	public void sendMessage(String message){
		
		if (this.view == VIEW_CONNECTED){
			
			ActionRequest req = new ActionRequest(ActionRequest.REQ_MESSAGE);
			req.setMessage(message);
			
			sendActionRequest(req);
			
		}
		else
			if (this.view == VIEW_GAME){
				
				GameRequest req = new GameRequest(GameRequest.REQ_MSG);
				req.setMsg(message);
				
				sendGameRequest(req);
				
			}
			
		
	}
	
	/**
	 * Quit from the current game
	 */
	public void quit(){
		
		
		GameRequest req = new GameRequest(GameRequest.REQ_QUIT);
		sendGameRequest(req);
		
		this.view = VIEW_CONNECTED;
		this.gamestate = STATE_NONE;
		
		playRequests.clear();
		messages.clear();
		
		update();
		
	}
	
	/**
	 * Shot at the given position of the enemy board
	 * 
	 * @param line
	 * @param column
	 * 
	 */
	public boolean shotAt(int line, int column) {
		
		
		if (view == VIEW_GAME && gamestate==STATE_PLAYING)
			if (enemyBoard.getStateAt(line, column)==Board.BOARD_EMPTY){

				GameRequest req = new GameRequest(GameRequest.REQ_PLAY);
				req.setLine(line);
				req.setColumn(column);

				sendGameRequest(req);

				myTurn = false;

				return true;
			}
			
			
		return false;
			
		
	}
	
	
	/**
	 * Send the configurated board to the server
	 */
	public void sendConfig(){
		
		GameRequest req = new GameRequest(GameRequest.REQ_CONFIG);
		req.setBoard(board);
		
		sendGameRequest(req);
		
	}
	
	private boolean sendConnectRequest(ConnectRequest req){
		
		try{
			out.writeObject(new ClientMessageID(ClientMessageID.CONNECT_REQUEST));
			out.writeObject(req);
			out.flush();
			return true;
		}catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
	}
	
	private boolean sendActionRequest(ActionRequest req){
		
		try{
			out.writeObject(new ClientMessageID(ClientMessageID.ACTION_REQUEST));
			out.writeObject(req);
			out.flush();
			return true;
		}catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
	}
	
	private boolean sendGameRequest(GameRequest req){
		
		try{
			out.writeObject(new ClientMessageID(ClientMessageID.GAME_REQUEST));
			out.writeObject(req);
			out.flush();
			return true;
		}catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
	}
	
	/**
	 * 
	 * Verify if there is any Play Request (= invitation) sent to the given username
	 */
	public boolean isPlayRequestSentTo(String username){
		
		for (PlayRequest pr : playRequests)
			if (pr.getTo().toUpperCase().equals(username.toUpperCase()))
				return true;
		
		return false;
	}
	
	/**
	 * 
	 * Verify if there is any Play Request (= invitation) received from the given username
	 */
	public boolean gotPlayRequestFrom(String username){
		
		for (PlayRequest pr : playRequests)
			if (pr.getFrom().toUpperCase().equals(username.toUpperCase()))
				return true;
		
		return false;
		
	}

	/**
	 * 
	 * Cancel a sent play request to the given username
	 */
	public void cancelInvite(String username) {
	
		ActionRequest req = new ActionRequest(ActionRequest.REQ_UNINVITE);
		req.setUsername(username);
		
		sendActionRequest(req);
		
		deletePlayRequestTo(username);
		
		update();
		
	}

	/**
	 * 
	 * Delete any play request sent to the given username
	 */
	public void deletePlayRequestTo(String username) {
		
		for (PlayRequest pr : playRequests)
			if (pr.getTo().toUpperCase().equals(username.toUpperCase())){
				playRequests.remove(pr);
				return;
			}

	}
	
	
	/**
	 * 
	 * Delete all play requests sent from the given user
	 */
	public void deletePlayRequestFrom(String username) {
		for (PlayRequest pr : playRequests)
			if (pr.getFrom().toUpperCase().equals(username.toUpperCase())){
				playRequests.remove(pr);
				return;
			}
		
	}
	
}
